package com.UnionPayCenter.account.service.impl;

import com.UnionPayCenter.account.entity.Account;
import com.UnionPayCenter.account.entity.AccountType;
import com.UnionPayCenter.account.enums.AccountErrorCodeEnum;
import com.UnionPayCenter.account.enums.AccountTypeEnum;
import com.UnionPayCenter.account.service.AccountService;
import com.UnionPayCenter.account.service.AccountTypeService;
import com.UnionPayCenter.constant.AccountConstants;
import com.UnionPayCenter.entity.dto.ChargeResponse;
import com.UnionPayCenter.mapper.AccountMapper;
import com.UnionPayCenter.util.Asserts;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.List;

@Slf4j
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Autowired
    private AccountTypeService accountTypeService;

    @Value("${acount.cas.timeout:100000}")
    private long casTimeout;

    @Override
    @Transactional
    public void createAccount(Account account) {

        Asserts.notNull(account, "账户信息");
        Asserts.naturalNumber(account.getShopId(), "店铺Id");
        Asserts.naturalNumber(account.getUserId(), "用户Id");

        List<AccountType> accountTypes = accountTypeService.getAccountTypeByShopId(account.getShopId());
        if (CollectionUtils.isEmpty(accountTypes)) {
            AccountErrorCodeEnum.ACCOUNT_TYPE_NOT_EXIST.exception();
        }

        for (AccountType accountType : accountTypes) {
            Account existedAccount = getAccountByUserId(account.getUserId(), accountType.getAccountType(), true);
            if (existedAccount != null) {
                continue;
            }
            Account newAccount = new Account();
            BeanUtils.copyProperties(account, newAccount);
            newAccount.setAccountTypeId(accountType.getAccountTypeId());
            newAccount.setAccountType(accountType.getAccountType());
            newAccount.setPriorityLevel(accountType.getPriorityLevel());
            newAccount.setBalance(0);
            newAccount.setOverdraft(0);
            newAccount.setFreeze(0);
            newAccount.setChargeAmount(0);
            newAccount.setChargeNum(0);
            newAccount.setPayAmount(0);
            newAccount.setPayNum(0);
            newAccount.setRefundNum(0);
            newAccount.setRefundAmount(0);
            newAccount.setCreateTime(new Date());
            newAccount.setUpdateTime(new Date());
            accountMapper.insert(newAccount);
            log.info("create account: {}", JSONObject.toJSON(newAccount));
        }
    }

    @Override
    public void freezeAccount(Long accountId) {
        Asserts.naturalNumber(accountId, "账户Id");
        Account account = getAccountByAccountId(accountId);
        account.setStatus(AccountConstants.STATUS_FREEZE);
        accountMapper.updateByPrimaryKeySelective(account);
        log.info("freeze account, accountId: {}", accountId);
    }

    @Override
    public void thawAccount(Long accountId) {
        Asserts.naturalNumber(accountId, "账户Id");
        Account account = getAccountByAccountId(accountId);
        account.setStatus(AccountConstants.STATUS_NORMAL);
        accountMapper.updateByPrimaryKeySelective(account);
        log.info("thaw account, accountId: {}", accountId);
    }

    @Override
    public void cancelAccount(Long accountId) {
        Asserts.naturalNumber(accountId, "账户Id");
        Account account = getAccountByAccountId(accountId);
        account.setStatus(AccountConstants.STATUS_CANCELED);
        accountMapper.updateByPrimaryKeySelective(account);
        log.info("cancel account, accountId {}", accountId);
    }

    @Override
    @Transactional
    public ChargeResponse charge(Long accountId, Integer amount, String subOpenId) {
        Asserts.naturalNumber(accountId, "账户Id");
        Asserts.naturalNumber(amount, "充值金额");

        boolean second = false;
        // 自旋，直到超时或更新成功
        int affectRows = 0;
        long currentTime = System.currentTimeMillis();
        while (affectRows == 0) {
            if ((System.currentTimeMillis() - currentTime) > casTimeout) {
                AccountErrorCodeEnum.ACCOUNT_CAS_TIMEOUT.exception();
            }
            Account account = getAccountByAccountId(accountId);
            if (second) {
                log.error("concurent update, accountId {}, thread name {}", account, Thread.currentThread().getName());
            }
            Integer balance = account.getBalance();
            Integer value = balance + amount;
            affectRows = accountMapper.cas(accountId, AccountConstants.BALANCE, balance, value);
            if (affectRows != 0) {
                Integer expectChargeNum = account.getChargeNum();
                Integer chargeNum = expectChargeNum + 1;
                Integer expectChargeAmount = account.getChargeAmount();
                Integer chargeAmount = expectChargeAmount + amount;
                accountMapper.cas(accountId, AccountConstants.CHARGE_NUM, expectChargeNum, chargeNum);
                accountMapper.cas(accountId, AccountConstants.CHARGE_AMOUNT, expectChargeAmount, chargeAmount);
            }
            second = true;
        }
        return null;
    }

    @Override
    public Boolean pay(Long accountId, Integer amount) {
        Asserts.naturalNumber(accountId, "账户Id");
        Asserts.naturalNumber(amount, "支付金额");

        // TODO 解决双花问题

        // 自旋，直到超时或更新成功
        int affectRows = 0;
        long currentTime = System.currentTimeMillis();
        boolean second = false;
        while (affectRows == 0) {
            if ((System.currentTimeMillis() - currentTime) > casTimeout) {
                AccountErrorCodeEnum.ACCOUNT_CAS_TIMEOUT.exception();
            }
            Account account = getAccountByAccountId(accountId);

            // 其他地方使用余额导致余额不足，支付失败
            Integer balance = account.getBalance();
            Integer value = balance - amount;
            if (value < 0) {
                AccountErrorCodeEnum.ACCOUNT_BALANCE.exception();
            }
            if (second) {
                log.error("concurent update, accountId {}, thread name {}", account, Thread.currentThread().getName());
            }
            affectRows = accountMapper.cas(accountId, AccountConstants.BALANCE, balance, value);
            if (affectRows != 0) {
                Integer expectPayNum = account.getPayNum();
                Integer payNum = expectPayNum + 1;
                Integer expectPayAmount = account.getPayAmount();
                Integer payAmount = expectPayAmount + amount;
                accountMapper.cas(accountId, AccountConstants.PAY_NUM, expectPayNum, payNum);
                accountMapper.cas(accountId, AccountConstants.PAY_AMOUNT, expectPayAmount, payAmount);
            }
            second = true;
        }
        return true;
    }

    @Override
    public Account getAccountByUserId(Integer userId, AccountTypeEnum accountType, boolean exception) {
        Asserts.naturalNumber(userId, "用户Id");
        Asserts.notNull(accountType, "账户类型");
        List<Account> accounts = accountMapper.selectByUserId(userId);
        if (CollectionUtils.isEmpty(accounts)) {
            return null;
        }

        // 根据账户类型查询个人账户
        for (Account account : accounts) {
            AccountType type = accountTypeService.getAccountTypeById(account.getAccountTypeId());
            if (type == null) {
                log.warn("account type is not exist, but account is created, account id [{}]", account.getAccountId());
                continue;
            }
            if (type != null && accountType.equals(type.getAccountType())) {
                return account;
            }
        }
        return null;
    }

    @Override
    public Account getAccountByUserId(Integer userId, AccountTypeEnum accountType) {
        Asserts.naturalNumber(userId, "用户Id");
        Asserts.notNull(accountType, "账户类型");
        List<Account> accounts = accountMapper.selectByUserId(userId);
        if (CollectionUtils.isEmpty(accounts)) {
            AccountErrorCodeEnum.ACCOUNT_NOT_EXIST.exception();
        }

        // 根据账户类型查询个人账户
        for (Account account : accounts) {
            AccountType type = accountTypeService.getAccountTypeById(account.getAccountTypeId());
            if (type == null) {
                log.warn("account type is not exist, but account is created, account id [{}]", account.getAccountId());
                continue;
            }
            if (type != null && accountType.equals(type.getAccountType())) {
                return account;
            }
        }
        AccountErrorCodeEnum.ACCOUNT_NOT_EXIST.exception();
        return null;
    }

    @Override
    public List<Account> getAccountByUserId(List<Integer> userIds, AccountTypeEnum... accountTypeEnums) {
        if (CollectionUtils.isEmpty(userIds)) {
            return Lists.newArrayListWithCapacity(0);
        }
        List<Account> accounts = accountMapper.selectByUserIds(userIds);
        if (CollectionUtils.isEmpty(accounts)) {
            AccountErrorCodeEnum.ACCOUNT_NOT_EXIST.exception();
        }
        return accounts;
    }

    @Override
    public Account getAccountByAccountId(Long accountId) {
        Account account = accountMapper.selectByPrimaryKey(accountId);
        if (account == null) {
            AccountErrorCodeEnum.ACCOUNT_NOT_EXIST.exception();
        }
        return account;
    }

    /**
     * 获取正常状态的账户，如果账户状态不正常，抛异常
     *
     * @param accountId 账户Id
     * @return 账户信息
     */
    private Account getNormalAccount(Long accountId) {
        Account account = getAccountByAccountId(accountId);
        if (account == null) {
            AccountErrorCodeEnum.ACCOUNT_NOT_EXIST.exception();
        }
        if (account.getStatus() != AccountConstants.STATUS_NORMAL) {
            AccountErrorCodeEnum.ACCOUNT_EXCEPTION.exception();
        }
        return account;
    }

}





